<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Pdf
 * @subpackage Actions
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: Action.php 18993 2009-11-15 17:09:16Z alexander $
 */


/** Internally used classes */
require_once 'Zend/Pdf/Element.php';
require_once 'Zend/Pdf/Element/Array.php';


/** Zend_Pdf_Target */
require_once 'Zend/Pdf/Target.php';


/**
 * Abstract PDF action representation class
 *
 * @package    Zend_Pdf
 * @subpackage Actions
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
abstract class Zend_Pdf_Action extends Zend_Pdf_Target implements RecursiveIterator, Countable
{
	/**
	 * Action dictionary
	 *
	 * @var Zend_Pdf_Element_Dictionary|Zend_Pdf_Element_Object|Zend_Pdf_Element_Reference
	 */
	protected $_actionDictionary;


	/**
	 * An original list of chained actions
	 *
	 * @var array  Array of Zend_Pdf_Action objects
	 */
	protected $_originalNextList;

	/**
	 * A list of next actions in actions tree (used for actions chaining)
	 *
	 * @var array  Array of Zend_Pdf_Action objects
	 */
	public $next = array();

	/**
	 * Object constructor
	 *
	 * @param Zend_Pdf_Element_Dictionary $dictionary
	 * @param SplObjectStorage            $processedActions  list of already processed action dictionaries, used to avoid cyclic references
	 * @throws Zend_Pdf_Exception
	 */
	public function __construct(Zend_Pdf_Element $dictionary, SplObjectStorage $processedActions)
	{
		require_once 'Zend/Pdf/Element.php';
		if ($dictionary->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception('$dictionary mast be a direct or an indirect dictionary object.');
		}

		$this->_actionDictionary = $dictionary;

		if ($dictionary->Next !== null) {
			if ($dictionary->Next instanceof Zend_Pdf_Element_Dictionary) {
				// Check if dictionary object is not already processed
				if (!$processedActions->contains($dictionary->Next)) {
					$processedActions->attach($dictionary->Next);
					$this->next[] = Zend_Pdf_Action::load($dictionary->Next, $processedActions);
				}
			} else if ($dictionary->Next instanceof Zend_Pdf_Element_Array) {
				foreach ($dictionary->Next->items as $chainedActionDictionary) {
					// Check if dictionary object is not already processed
					if (!$processedActions->contains($chainedActionDictionary)) {
						$processedActions->attach($chainedActionDictionary);
						$this->next[] = Zend_Pdf_Action::load($chainedActionDictionary, $processedActions);
					}
				}
			} else {
				require_once 'Zend/Pdf/Exception.php';
				throw new Zend_Pdf_Exception('PDF Action dictionary Next entry must be a dictionary or an array.');
			}
		}

		$this->_originalNextList = $this->next;
	}

	/**
	 * Load PDF action object using specified dictionary
	 *
	 * @internal
	 * @param Zend_Pdf_Element $dictionary (It's actually Dictionary or Dictionary Object or Reference to a Dictionary Object)
	 * @param SplObjectStorage $processedActions  list of already processed action dictionaries, used to avoid cyclic references
	 * @return Zend_Pdf_Action
	 * @throws Zend_Pdf_Exception
	 */
	public static function load(Zend_Pdf_Element $dictionary, SplObjectStorage $processedActions = null)
	{
		if ($processedActions === null) {
			$processedActions = new SplObjectStorage();
		}

		require_once 'Zend/Pdf/Element.php';
		if ($dictionary->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception('$dictionary mast be a direct or an indirect dictionary object.');
		}
		if (isset($dictionary->Type)  &&  $dictionary->Type->value != 'Action') {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception('Action dictionary Type entry must be set to \'Action\'.');
		}

		if ($dictionary->S === null) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception('Action dictionary must contain S entry');
		}

		switch ($dictionary->S->value) {
			case 'GoTo':
				require_once 'Zend/Pdf/Action/GoTo.php';
				return new Zend_Pdf_Action_GoTo($dictionary, $processedActions);
				brake;

			case 'GoToR':
				require_once 'Zend/Pdf/Action/GoToR.php';
				return new Zend_Pdf_Action_GoToR($dictionary, $processedActions);
				brake;

			case 'GoToE':
				require_once 'Zend/Pdf/Action/GoToE.php';
				return new Zend_Pdf_Action_GoToE($dictionary, $processedActions);
				brake;

			case 'Launch':
				require_once 'Zend/Pdf/Action/Launch.php';
				return new Zend_Pdf_Action_Launch($dictionary, $processedActions);
				brake;

			case 'Thread':
				require_once 'Zend/Pdf/Action/Thread.php';
				return new Zend_Pdf_Action_Thread($dictionary, $processedActions);
				brake;

			case 'URI':
				require_once 'Zend/Pdf/Action/URI.php';
				return new Zend_Pdf_Action_URI($dictionary, $processedActions);
				brake;

			case 'Sound':
				require_once 'Zend/Pdf/Action/Sound.php';
				return new Zend_Pdf_Action_Sound($dictionary, $processedActions);
				brake;

			case 'Movie':
				require_once 'Zend/Pdf/Action/Movie.php';
				return new Zend_Pdf_Action_Movie($dictionary, $processedActions);
				brake;

			case 'Hide':
				require_once 'Zend/Pdf/Action/Hide.php';
				return new Zend_Pdf_Action_Hide($dictionary, $processedActions);
				brake;

			case 'Named':
				require_once 'Zend/Pdf/Action/Named.php';
				return new Zend_Pdf_Action_Named($dictionary, $processedActions);
				brake;

			case 'SubmitForm':
				require_once 'Zend/Pdf/Action/SubmitForm.php';
				return new Zend_Pdf_Action_SubmitForm($dictionary, $processedActions);
				brake;

			case 'ResetForm':
				require_once 'Zend/Pdf/Action/ResetForm.php';
				return new Zend_Pdf_Action_ResetForm($dictionary, $processedActions);
				brake;

			case 'ImportData':
				require_once 'Zend/Pdf/Action/ImportData.php';
				return new Zend_Pdf_Action_ImportData($dictionary, $processedActions);
				brake;

			case 'JavaScript':
				require_once 'Zend/Pdf/Action/JavaScript.php';
				return new Zend_Pdf_Action_JavaScript($dictionary, $processedActions);
				brake;

			case 'SetOCGState':
				require_once 'Zend/Pdf/Action/SetOCGState.php';
				return new Zend_Pdf_Action_SetOCGState($dictionary, $processedActions);
				brake;

			case 'Rendition':
				require_once 'Zend/Pdf/Action/Rendition.php';
				return new Zend_Pdf_Action_Rendition($dictionary, $processedActions);
				brake;

			case 'Trans':
				require_once 'Zend/Pdf/Action/Trans.php';
				return new Zend_Pdf_Action_Trans($dictionary, $processedActions);
				brake;

			case 'GoTo3DView':
				require_once 'Zend/Pdf/Action/GoTo3DView.php';
				return new Zend_Pdf_Action_GoTo3DView($dictionary, $processedActions);
				brake;

			default:
				require_once 'Zend/Pdf/Action/Unknown.php';
				return new Zend_Pdf_Action_Unknown($dictionary, $processedActions);
				brake;
		}
	}

	/**
	 * Get resource
	 *
	 * @internal
	 * @return Zend_Pdf_Element
	 */
	public function getResource()
	{
		return $this->_actionDictionary;
	}

	/**
	 * Dump Action and its child actions into PDF structures
	 *
	 * Returns dictionary indirect object or reference
	 *
	 * @internal
	 * @param Zend_Pdf_ElementFactory $factory   Object factory for newly created indirect objects
	 * @param SplObjectStorage $processedActions  list of already processed actions (used to prevent infinity loop caused by cyclic references)
	 * @return Zend_Pdf_Element_Object|Zend_Pdf_Element_Reference   Dictionary indirect object
	 */
	public function dumpAction(Zend_Pdf_ElementFactory_Interface $factory, SplObjectStorage $processedActions = null)
	{
		if ($processedActions === null) {
			$processedActions = new SplObjectStorage();
		}
		if ($processedActions->contains($this)) {
			require_once 'Zend/Pdf/Exception.php';
			throw new Zend_Pdf_Exception('Action chain cyclyc reference is detected.');
		}
		$processedActions->attach($this);

		$childListUpdated = false;
		if (count($this->_originalNextList) != count($this->next)) {
			// If original and current children arrays have different size then children list was updated
			$childListUpdated = true;
		} else if ( !(array_keys($this->_originalNextList) === array_keys($this->next)) ) {
			// If original and current children arrays have different keys (with a glance to an order) then children list was updated
			$childListUpdated = true;
		} else {
			foreach ($this->next as $key => $childAction) {
				if ($this->_originalNextList[$key] !== $childAction) {
					$childListUpdated = true;
					break;
				}
			}
		}

		if ($childListUpdated) {
			$this->_actionDictionary->touch();
			switch (count($this->next)) {
				case 0:
					$this->_actionDictionary->Next = null;
					break;

				case 1:
					$child = reset($this->next);
					$this->_actionDictionary->Next = $child->dumpAction($factory, $processedActions);
					break;

				default:
					require_once 'Zend/Pdf/Element/Array.php';
					$pdfChildArray = new Zend_Pdf_Element_Array();
					foreach ($this->next as $child) {

						$pdfChildArray->items[] = $child->dumpAction($factory, $processedActions);
					}
					$this->_actionDictionary->Next = $pdfChildArray;
					break;
			}
		} else {
			foreach ($this->next as $child) {
				$child->dumpAction($factory, $processedActions);
			}
		}

		if ($this->_actionDictionary instanceof Zend_Pdf_Element_Dictionary) {
			// It's a newly created action. Register it within object factory and return indirect object
			return $factory->newObject($this->_actionDictionary);
		} else {
			// It's a loaded object
			return $this->_actionDictionary;
		}
	}


	////////////////////////////////////////////////////////////////////////
	//  RecursiveIterator interface methods
	//////////////

	/**
	 * Returns current child action.
	 *
	 * @return Zend_Pdf_Action
	 */
	public function current()
	{
		return current($this->next);
	}

	/**
	 * Returns current iterator key
	 *
	 * @return integer
	 */
	public function key()
	{
		return key($this->next);
	}

	/**
	 * Go to next child
	 */
	public function next()
	{
		return next($this->next);
	}

	/**
	 * Rewind children
	 */
	public function rewind()
	{
		return reset($this->next);
	}

	/**
	 * Check if current position is valid
	 *
	 * @return boolean
	 */
	public function valid()
	{
		return current($this->next) !== false;
	}

	/**
	 * Returns the child action.
	 *
	 * @return Zend_Pdf_Action|null
	 */
	public function getChildren()
	{
		return current($this->next);
	}

	/**
	 * Implements RecursiveIterator interface.
	 *
	 * @return bool  whether container has any pages
	 */
	public function hasChildren()
	{
		return count($this->next) > 0;
	}


	////////////////////////////////////////////////////////////////////////
	//  Countable interface methods
	//////////////

	/**
	 * count()
	 *
	 * @return int
	 */
	public function count()
	{
		return count($this->childOutlines);
	}
}
